package com.cxh.supermarket.system.service;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cxh.supermarket.common.constant.CacheConstants;
import com.cxh.supermarket.common.constant.CacheNames;
import com.cxh.supermarket.common.constant.Constants;
import com.cxh.supermarket.common.constant.HttpStatus;
import com.cxh.supermarket.common.constant.IMConstants;
import com.cxh.supermarket.common.constant.UserConstants;
import com.cxh.supermarket.common.core.domain.dto.RoleDTO;
import com.cxh.supermarket.common.core.domain.dto.UserOnlineDTO;
import com.cxh.supermarket.common.core.domain.entity.SysUser;
import com.cxh.supermarket.common.core.domain.event.LogininforEvent;
import com.cxh.supermarket.common.core.domain.model.LoginUser;
import com.cxh.supermarket.common.core.domain.model.XcxLoginUser;
import com.cxh.supermarket.common.core.domain.wechat.WxAppLoginResp;
import com.cxh.supermarket.common.core.domain.wechat.XcxLoginResp;
import com.cxh.supermarket.common.enums.DeviceType;
import com.cxh.supermarket.common.enums.LoginType;
import com.cxh.supermarket.common.enums.UserStatus;
import com.cxh.supermarket.common.enums.UserType;
import com.cxh.supermarket.common.exception.ServiceException;
import com.cxh.supermarket.common.exception.user.CaptchaException;
import com.cxh.supermarket.common.exception.user.CaptchaExpireException;
import com.cxh.supermarket.common.exception.user.UserException;
import com.cxh.supermarket.common.helper.LoginHelper;
import com.cxh.supermarket.common.utils.AppOneClickUtil;
import com.cxh.supermarket.common.utils.DateUtils;
import com.cxh.supermarket.common.utils.JudgeUtils;
import com.cxh.supermarket.common.utils.MessageUtils;
import com.cxh.supermarket.common.utils.ServletUtils;
import com.cxh.supermarket.common.utils.StringUtils;
import com.cxh.supermarket.common.utils.WxUtil;
import com.cxh.supermarket.common.utils.redis.CacheUtils;
import com.cxh.supermarket.common.utils.redis.RedisUtils;
import com.cxh.supermarket.common.utils.spring.SpringUtils;
import com.cxh.supermarket.im.IMUtil;
import com.cxh.supermarket.system.mapper.SysUserMapper;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * 登录校验方法
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLoginService {

  private final SysUserMapper userMapper;
  private final ISysConfigService configService;
  private final ISysUserService userService;
  private final SysPermissionService permissionService;

  @Value("${user.password.maxRetryCount}")
  private Integer maxRetryCount;

  @Value("${user.password.lockTime}")
  private Integer lockTime;

  /**
   * 登录验证
   *
   * @param username 用户名
   * @param password 密码
   * @param code     验证码
   * @param uuid     唯一标识
   * @return 结果
   */
  public String login(String username, String password, String code, String uuid) {
    boolean captchaEnabled = configService.selectCaptchaEnabled();
    // 验证码开关
    if (captchaEnabled) {
      validateCaptcha(username, code, uuid);
    }
    // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
    SysUser user = loadUserByUsername(username);
    checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
    // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
    LoginUser loginUser = buildLoginUser(user);
    // 生成token
    LoginHelper.loginByDevice(loginUser, DeviceType.PC);

    recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), username);
    return StpUtil.getTokenValue();
  }

  public String smsLogin(String phonenumber, String smsCode) {
    // 通过手机号查找用户
    SysUser user = loadUserByPhonenumber(phonenumber);

    checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
    // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
    LoginUser loginUser = buildLoginUser(user);
    // 生成token
    LoginHelper.loginByDevice(loginUser, DeviceType.APP);

    recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), user.getUserName());
    return StpUtil.getTokenValue();
  }

  /**
   * 手机号登录(无账号则注册)
   *
   * @param phonenumber
   * @param smsCode
   * @return
   */
  public String smsLoginOrRegister(String phonenumber, String smsCode) {
    // 通过手机号查找用户
    SysUser user = findUserByPhonenumber(phonenumber);
    //没有用户创建用户
    if (ObjectUtil.isNull(user)) {
      checkSms(phonenumber, smsCode);
      SysUser sysUser = new SysUser();
      user = getSysUser(phonenumber, sysUser);
    }
    checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));

    // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
    LoginUser loginUser = buildLoginUser(user);
    // 生成token
    LoginHelper.loginByDevice(loginUser, DeviceType.APP);

    recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), user.getUserName());
    return StpUtil.getTokenValue();
  }

  private SysUser getSysUser(String phonenumber, SysUser sysUser) {
    SysUser user;
    sysUser.setUserName(phonenumber);
    sysUser.setNickName(UserConstants.DEFAULT_NICK_NAME);
    sysUser.setPhonenumber(phonenumber);
    sysUser.setUserType(UserType.APP_USER.getUserType());
    boolean regFlag = userService.registerUser(sysUser);
    if (!regFlag) {
      throw new UserException("user.register.error");
    }
    user = findUserByPhonenumber(phonenumber);
    return user;
  }

  /**
   * 手机号一键登录
   *
   * @param accessToken 阿里云token
   * @return token
   */
  public String appOneClick(String accessToken) {
    String phonenumber = AppOneClickUtil.login(accessToken);
    // 通过手机号查找用户
    SysUser user = findUserByPhonenumber(phonenumber);

    //没有用户创建用户
    if (ObjectUtil.isNull(user)) {
      SysUser sysUser = new SysUser();
      user = getSysUser(phonenumber, sysUser);
    }

    // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
    LoginUser loginUser = buildLoginUser(user);
    // 生成token
    LoginHelper.loginByDevice(loginUser, DeviceType.APP);

    recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), user.getUserName());
    return StpUtil.getTokenValue();
  }


  public String emailLogin(String email, String emailCode) {
    // 通过手邮箱查找用户
    SysUser user = loadUserByEmail(email);

    checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
    // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
    LoginUser loginUser = buildLoginUser(user);
    // 生成token
    LoginHelper.loginByDevice(loginUser, DeviceType.APP);

    recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), user.getUserName());
    return StpUtil.getTokenValue();
  }

  public Map<String, Object> xcxLogin(String code) {
    //校验 appid + appsrcret + code 调用登录凭证校验接口 获取 session_key 与 openid
    XcxLoginResp xcxLoginResp = WxUtil.xcxLogin(code);
    //判断unionid不能为空是否为空
    String unionid = xcxLoginResp.getUnionid();
    if (ObjectUtil.isNull(unionid)) {
      throw new ServiceException("unionid.is.empty");
    }

    //查询用户信息是否存在
    LambdaQueryWrapper<SysUser> sysUserLambdaQueryWrapper = new LambdaQueryWrapper<SysUser>()
        .eq(SysUser::getUnionid, unionid).select(SysUser::getUserId, SysUser::getPhonenumber);
    SysUser checkUser = userMapper.selectOne(sysUserLambdaQueryWrapper);

    if (ObjectUtil.isNull(checkUser)) {
      //未绑定unionid
      CacheUtils.put(CacheNames.XCX_LOGIN_RESP, code, xcxLoginResp);
      throw new ServiceException("unionid.is.not.bind", HttpStatus.UNBOUND);
    }

    //查询用户信息
    SysUser user = findUserByPhonenumber(checkUser.getPhonenumber());
    //构建登录实体
    LoginUser loginUser = buildLoginUser(user);
    XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
    userInfo.setOpenid(user.getOpenid());
    // 生成token
    LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
    //记录登陆信息
    this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), user.getUserName());
    //响应
    Map<String, Object> ajax = new HashMap<>();
    ajax.put(Constants.TOKEN, StpUtil.getTokenValue());
    ajax.put("phoneNumber", user.getPhonenumber());
    ajax.put("secretKey", user.getSecretKey());
    //修改临时有效期
//        LoginHelper.updateActivityToken(StpUtil.getTokenValue(), 1800L);
    return ajax;
  }

  /**
   * 第一次小程序登陆
   *
   * @param loginCode 小程序登陆凭证
   * @param phoneCode 小程序获取手机号凭证
   * @return token
   */
  public Map<String, Object> xcxFirstLogin(String loginCode, String phoneCode) {
    //获取用户信息
    XcxLoginResp xcxLoginResp = CacheUtils.get(CacheNames.XCX_LOGIN_RESP, loginCode);
    JudgeUtils.conditionException(ObjectUtil.isNull(xcxLoginResp)).errMsg("请在5分钟内授权");
    //获取微信手机号
    String phoneNumber = WxUtil.xcxGetPhoneNumber(phoneCode);
    // 通过手机号查找用户
    SysUser user = findUserByPhonenumber(phoneNumber);

    //没有用户创建用户
    if (ObjectUtil.isNull(user)) {
      SysUser sysUser = new SysUser();
      sysUser.setUserName(phoneNumber);
      sysUser.setNickName(UserConstants.DEFAULT_NICK_NAME);
      sysUser.setPhonenumber(phoneNumber);
      sysUser.setUserType(UserType.APP_USER.getUserType());
      sysUser.setOpenid(Objects.requireNonNull(xcxLoginResp).getOpenid());
      sysUser.setUnionid(xcxLoginResp.getUnionid());
      boolean regFlag = userService.registerUser(sysUser);
      if (!regFlag) {
        throw new UserException("user.register.error");
      }
      user = findUserByPhonenumber(phoneNumber);
    } else {
      //之前登陆过, 绑定微信唯一标识
      String secretKey = IdUtil.simpleUUID();
      Boolean bindResult = userService.bindWxInfo(user.getUserId(), xcxLoginResp.getOpenid(), xcxLoginResp.getUnionid(), secretKey);
      if (BooleanUtil.isFalse(bindResult)) {
        throw new ServiceException("此手机号已和其他微信绑定");
      }
      user.setSecretKey(secretKey);
    }
    CacheUtils.evict(CacheNames.XCX_LOGIN_RESP, loginCode);

    //构建登录实体
    LoginUser loginUser = buildLoginUser(user);
    XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
    userInfo.setOpenid(user.getOpenid());
    // 生成token
    LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
    //记录登陆信息
    this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), user.getUserName());
    //响应
    Map<String, Object> ajax = new HashMap<>();
    ajax.put(Constants.TOKEN, StpUtil.getTokenValue());
    ajax.put("phoneNumber", user.getPhonenumber());
    ajax.put("secretKey", user.getSecretKey());
    //修改临时有效期
//        LoginHelper.updateActivityToken(StpUtil.getTokenValue(), 1800L);
    return ajax;
  }

  /**
   * 退出登录
   */
  public void logout() {
    try {
      LoginUser loginUser = LoginHelper.getLoginUser();
      recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
    } catch (NotLoginException ignored) {
    } finally {
      try {
        StpUtil.logout();
      } catch (NotLoginException ignored) {
      }
    }
  }

  /**
   * 记录登录信息
   *
   * @param username 用户名
   * @param status   状态
   * @param message  消息内容
   */
  private void recordLogininfor(String username, String status, String message) {
    LogininforEvent logininforEvent = new LogininforEvent();
    logininforEvent.setUsername(username);
    logininforEvent.setStatus(status);
    logininforEvent.setMessage(message);
    logininforEvent.setRequest(ServletUtils.getRequest());
    SpringUtils.context().publishEvent(logininforEvent);
  }

  /**
   * 校验短信验证码
   */
  public boolean validateSmsCode(String phonenumber, String smsCode) {
    if (this.captchaAnon(smsCode)) {
      String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
      if (StringUtils.isBlank(code)) {
        recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
        throw new CaptchaExpireException();
      }
      return code.equals(smsCode);
    }
    return true;
  }

  /**
   * 校验验证码
   *
   * @param phonenumber 手机号
   * @param smsCode     验证码
   */
  public void checkSms(String phonenumber, String smsCode) {
    boolean checkSms = validateSmsCode(phonenumber, smsCode);
    if (!checkSms) {
      throw new ServiceException("验证码错误");
    }
  }

  /**
   * 验证码后门放行
   *
   * @param code 验证码
   * @return 判断 true: 不放行   false:放行
   */
  private boolean captchaAnon(String code) {
    try {
      SpringUtils.getBean("captchaBackdoor");
      if (code.equals("6666")) {
        return false;
      }
    } catch (BeansException e) {
      //无bean
    }
    return true;
  }

  /**
   * 校验邮箱验证码
   */
  private boolean validateEmailCode(String email, String emailCode) {
    String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + email);
    if (StringUtils.isBlank(code)) {
      recordLogininfor(email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
      throw new CaptchaExpireException();
    }
    return code.equals(emailCode);
  }

  /**
   * 校验验证码
   *
   * @param username 用户名
   * @param code     验证码
   * @param uuid     唯一标识
   */
  public void validateCaptcha(String username, String code, String uuid) {
    String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
    String captcha = RedisUtils.getCacheObject(verifyKey);
    RedisUtils.deleteObject(verifyKey);
    if (captcha == null) {
      recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
      throw new CaptchaExpireException();
    }
    if (!code.equalsIgnoreCase(captcha)) {
      recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
      throw new CaptchaException();
    }
  }

  private SysUser loadUserByUsername(String username) {
    SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
        .select(SysUser::getUserName, SysUser::getStatus)
        .eq(SysUser::getUserName, username));
    if (ObjectUtil.isNull(user)) {
      log.info("登录用户：{} 不存在.", username);
      throw new UserException("user.not.exists", username);
    } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
      log.info("登录用户：{} 已被停用.", username);
      throw new UserException("user.blocked", username);
    }
    return userMapper.selectUserByUserName(username);
  }

  private SysUser loadUserByPhonenumber(String phonenumber) {
    SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
        .select(SysUser::getPhonenumber, SysUser::getStatus)
        .eq(SysUser::getPhonenumber, phonenumber));
    if (ObjectUtil.isNull(user)) {
      log.info("登录用户：{} 不存在.", phonenumber);
      throw new UserException("user.not.exists", phonenumber);
    } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
      log.info("登录用户：{} 已被停用.", phonenumber);
      throw new UserException("user.blocked", phonenumber);
    }
    return userMapper.selectUserByPhonenumber(phonenumber);
  }

  private SysUser loadUserByEmail(String email) {
    SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
        .select(SysUser::getPhonenumber, SysUser::getStatus)
        .eq(SysUser::getEmail, email));
    if (ObjectUtil.isNull(user)) {
      log.info("登录用户：{} 不存在.", email);
      throw new UserException("user.not.exists", email);
    } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
      log.info("登录用户：{} 已被停用.", email);
      throw new UserException("user.blocked", email);
    }
    return userMapper.selectUserByEmail(email);
  }

  private SysUser loadUserByOpenid(String openid) {
    // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
    // todo 自行实现 userService.selectUserByOpenid(openid);
    SysUser user = new SysUser();
    if (ObjectUtil.isNull(user)) {
      log.info("登录用户：{} 不存在.", openid);
      // todo 用户不存在 业务逻辑自行实现
    } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
      log.info("登录用户：{} 已被停用.", openid);
      // todo 用户已被停用 业务逻辑自行实现
    }
    return user;
  }

  /**
   * 构建登录用户
   */
  private LoginUser buildLoginUser(SysUser user) {
    LoginUser loginUser = new LoginUser();
    loginUser.setUserId(user.getUserId());
    loginUser.setDeptId(user.getDeptId());
    loginUser.setUsername(user.getUserName());
    loginUser.setUserType(user.getUserType());
    loginUser.setMenuPermission(permissionService.getMenuPermission(user));
    loginUser.setRolePermission(permissionService.getRolePermission(user));
    loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
    List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
    loginUser.setRoles(roles);
    return loginUser;
  }

  /**
   * 记录登录信息
   *
   * @param userId 用户ID
   */
  public void recordLoginInfo(Long userId, String username) {
    SysUser sysUser = new SysUser();
    sysUser.setUserId(userId);
    sysUser.setLoginIp(ServletUtils.getClientIP());
    sysUser.setLoginDate(DateUtils.getNowDate());
    sysUser.setUpdateBy(username);
    userMapper.updateById(sysUser);
    this.sendCustomerServiceMessage(userId);
  }

  /**
   * 登录校验
   */
  private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
    String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
    String loginFail = Constants.LOGIN_FAIL;

    // 获取用户登录错误次数，默认为0 (可自定义限制策略 例如: key + username + ip)
    int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
    // 锁定时间内登录 则踢出
    if (errorNumber >= maxRetryCount) {
      recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
      throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
    }

    if (supplier.get()) {
      // 错误次数递增
      errorNumber++;
      RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
      // 达到规定错误次数 则锁定登录
      if (errorNumber >= maxRetryCount) {
        recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
        throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
      } else {
        // 未达到规定错误次数
        recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
        throw new UserException(loginType.getRetryLimitCount(), errorNumber);
      }
    }

    // 登录成功 清空错误次数
    RedisUtils.deleteObject(errorKey);
  }


  private SysUser findUserByPhonenumber(String phonenumber) {
    SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
        .select(SysUser::getPhonenumber, SysUser::getStatus)
        .eq(SysUser::getPhonenumber, phonenumber));
    if (ObjectUtil.isNull(user)) {
      return user;
    } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
      log.info("登录用户：{} 已被停用.", phonenumber);
      throw new UserException("user.blocked", phonenumber);
    }
    return userMapper.selectUserByPhonenumber(phonenumber);
  }

  /**
   * 获取在线用户列表
   *
   * @return
   */
  public List<UserOnlineDTO> getUserOnlineList() {
    // 获取所有未过期的 token
    List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
    List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
    for (String key : keys) {
      String token = StringUtils.substringAfterLast(key, ":");
      // 如果已经过期则跳过
      if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
        continue;
      }
      userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token));
    }
    //移除空的在线列表
    userOnlineDTOList.removeAll(Collections.singleton(null));

    //根据手机号获取在线用户列表
/*   userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
              StringUtils.equals(userName, userOnline.getUserName()));

    if(userOnlineDTOList.isEmpty()){
      return false;
    }*/
    return userOnlineDTOList;
  }

  /**
   * 获取发布招工人（职位）手机号
   *
   * @param userId
   * @return
   */
  public String jobPhonenumber(Long userId) {
    return userMapper.jobPhonenumber(userId);
  }



  /**
   * app微信登录
   *
   * @param code app微信登陆code
   * @return token
   */
  public String appLogin(String code) {
    //登录
    WxAppLoginResp wxAppLoginResp = WxUtil.appLogin(code);
    //判断unionid是否为空
    String unionid = wxAppLoginResp.getUnionid();
    if (ObjectUtil.isNull(unionid)) {
      throw new ServiceException("unionid.is.empty");
    }

    //查询用户信息是否存在
    LambdaQueryWrapper<SysUser> sysUserLambdaQueryWrapper = new LambdaQueryWrapper<SysUser>()
        .eq(SysUser::getUnionid, unionid).select(SysUser::getUserId, SysUser::getPhonenumber);
    SysUser checkUser = userMapper.selectOne(sysUserLambdaQueryWrapper);

    if (ObjectUtil.isNull(checkUser)) {
      //未绑定unionid
      CacheUtils.put(CacheNames.APP_WX_LOGIN_RESP, code, wxAppLoginResp);
      throw new ServiceException("unionid.is.not.bind", HttpStatus.UNBOUND);
    }

    //查询用户信息
    SysUser user = findUserByPhonenumber(checkUser.getPhonenumber());
    //构建登录实体
    LoginUser loginUser = buildLoginUser(user);
    XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
    userInfo.setOpenid(user.getOpenid());
    // 生成token
    LoginHelper.loginByDevice(userInfo, DeviceType.APP);
    //记录登陆信息
    this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), user.getUserName());
    //响应
    return StpUtil.getTokenValue();
  }

  /**
   * 第一次app登录,绑定手机号
   *
   * @param code        微信登陆凭证
   * @param phoneNumber 手机号
   * @return token
   */
  public String wxAppFirstLogin(String code, String phoneNumber, String smsCode) {
    //校验短信验证码
    checkSms(phoneNumber, smsCode);
    //获取用户信息
    WxAppLoginResp wxAppLoginResp = CacheUtils.get(CacheNames.APP_WX_LOGIN_RESP, code);
    JudgeUtils.conditionException(ObjectUtil.isNull(wxAppLoginResp)).errMsg("请在5分钟内授权");
    // 通过手机号查找用户
    SysUser user = findUserByPhonenumber(phoneNumber);

    //没有用户创建用户
    if (ObjectUtil.isNull(user)) {
      SysUser sysUser = new SysUser();
      sysUser.setUserName(phoneNumber);
      sysUser.setNickName(UserConstants.DEFAULT_NICK_NAME);
      sysUser.setPhonenumber(phoneNumber);
      sysUser.setUserType(UserType.APP_USER.getUserType());
      sysUser.setOpenid(Objects.requireNonNull(wxAppLoginResp).getOpenid());
      sysUser.setUnionid(wxAppLoginResp.getUnionid());
      boolean regFlag = userService.registerUser(sysUser);
      if (!regFlag) {
        throw new UserException("user.register.error");
      }
      user = findUserByPhonenumber(phoneNumber);
    } else {
      //之前登陆过, 绑定微信唯一标识
      String secretKey = IdUtil.simpleUUID();
      Boolean bindResult =
          userService.bindWxInfo(user.getUserId(), wxAppLoginResp.getOpenid(), wxAppLoginResp.getUnionid(), secretKey);
      if (BooleanUtil.isFalse(bindResult)) {
        throw new ServiceException("此手机号已和其他微信绑定");
      }
      user.setSecretKey(secretKey);
    }
    CacheUtils.evict(CacheNames.APP_WX_LOGIN_RESP, code);

    //构建登录实体
    LoginUser loginUser = buildLoginUser(user);
    XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
    userInfo.setOpenid(user.getOpenid());
    // 生成token
    LoginHelper.loginByDevice(userInfo, DeviceType.APP);
    //记录登陆信息
    this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
    recordLoginInfo(user.getUserId(), user.getUserName());

    return StpUtil.getTokenValue();
  }

  private void sendCustomerServiceMessage(Long userId) {
    //发送客服消息
    IMUtil.sendTextMessageToUser(Collections.singletonList(userId), IMConstants.FROM_LABOR_ADMIN, "您好,请问有什么可以帮助您",
        UserConstants.WORKER);
    IMUtil.sendTextMessageToUser(Collections.singletonList(userId), IMConstants.FROM_LABOR_ADMIN, "您好,请问有什么可以帮助您",
        UserConstants.EMPLOYER);
  }

}
